//
//  Copyright (c) 2018. Uber Technologies
//
//  Licensed under the Apache License, Version 2.0 (the "License");
//  you may not use this file except in compliance with the License.
//  You may obtain a copy of the License at
//
//  http://www.apache.org/licenses/LICENSE-2.0
//
//  Unless required by applicable law or agreed to in writing, software
//  distributed under the License is distributed on an "AS IS" BASIS,
//  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
//  See the License for the specific language governing permissions and
//  limitations under the License.
//

import Foundation

import XCTest
@testable import NeedleFramework

class PluginizedDependencyGraphExporterTests: AbstractPluginizedGeneratorTests {

    let fixturesURL = URL(fileURLWithPath: #file).deletingLastPathComponent().deletingLastPathComponent().deletingLastPathComponent().appendingPathComponent("Fixtures/Pluginized")
    let outputURL = FileManager.default.temporaryDirectory.appendingPathComponent("generated.swift")

    override func setUp() {
        let created = FileManager.default.createFile(atPath: outputURL.path, contents: nil, attributes: nil)
        XCTAssert(created)
    }

    override func tearDown() {
        try? FileManager.default.removeItem(atPath: outputURL.path)
    }

    @available(OSX 10.12, *)
    func test_export_verifyContent() throws {
        let (components, pluginizedComponents, imports, needleHash, allDeps) = pluginizedSampleProjectParsed()
        let executor = MockSequenceExecutor()
        let exporter = PluginizedDependencyGraphExporter()

        let headerDocPath = fixturesURL.deletingLastPathComponent().appendingPathComponent("HeaderDoc.txt").path
        try? exporter.export(components, pluginizedComponents, with: imports, to: outputURL.path, using: executor, withTimeout: 10, include: headerDocPath, needleVersionHash: needleHash)
        let generated = try String(contentsOf: outputURL)
        XCTAssertNotNil(generated, "Could not read the generated file")
        XCTAssertTrue(generated.contains("//\n//  Copyright © Uber Technologies, Inc. All rights reserved.\n//\n//  @generated by Needle\n//  swiftlint:disable custom_rules"))
        XCTAssertTrue(generated.contains("import NeedleFoundation"))
        XCTAssertTrue(generated.contains("import RxSwift"))
        XCTAssertTrue(generated.contains("import UIKit"))
        XCTAssertTrue(generated.contains("import ScoreSheet"))
        XCTAssertTrue(generated.contains("import TicTacToeIntegrations"))
        XCTAssertTrue(generated.contains("private let needleDependenciesHash : String? = \"86deb40d0ec1c9fc9fd5e5e8fc17a167\""))
        XCTAssertTrue(generated.contains("// MARK: - Registration"))
        XCTAssertTrue(generated.contains("""
    registerProviderFactory(\"^->RootComponent->LoggedOutComponent\", factory1434ff4463106e5c4f1bb3a8f24c1d289f2c0f2e)
"""))
        XCTAssertTrue(generated.contains("""
    registerProviderFactory("^->RootComponent", factoryEmptyDependencyProvider)
"""))
        XCTAssertTrue(generated.contains("""
    registerProviderFactory(\"^->RootComponent->LoggedInComponent->GameComponent->GameNonCoreComponent->ScoreSheetComponent\", factoryb11b7d1dec7e3c9b3dca49b41e44e0ed6a6f8eaf)
"""))
        XCTAssertTrue(generated.contains("""
    registerProviderFactory(\"^->RootComponent->LoggedInComponent->LoggedInNonCoreComponent->ScoreSheetComponent\", factory3306c50e89e2421d0b0c65d055996113f3c13de1)
"""))
        XCTAssertTrue(generated.contains("""
    registerProviderFactory(\"^->RootComponent->LoggedInComponent->GameComponent->GameNonCoreComponent\", factoryEmptyDependencyProvider)
"""))
        XCTAssertTrue(generated.contains("""
    registerProviderFactory(\"^->RootComponent->LoggedInComponent->LoggedInNonCoreComponent\", factoryEmptyDependencyProvider)
"""))
        XCTAssertTrue(generated.contains("""
    registerProviderFactory(\"^->RootComponent->LoggedInComponent->GameComponent\", factorycf9c02c4def4e3d508816cd03d3cf415b70dfb0e)
"""))
        XCTAssertTrue(generated.contains("""
    registerProviderFactory(\"^->RootComponent->LoggedInComponent\", factoryEmptyDependencyProvider)
"""))
        XCTAssertTrue(generated.contains("""
    __PluginExtensionProviderRegistry.instance.registerPluginExtensionProviderFactory(for: \"GameComponent\") { component in
        return GamePluginExtensionProvider(component: component)
    }
"""))
        XCTAssertTrue(generated.contains("""
    __PluginExtensionProviderRegistry.instance.registerPluginExtensionProviderFactory(for: \"LoggedInComponent\") { component in
        return LoggedInPluginExtensionProvider(component: component)
    }
"""))
        XCTAssertTrue(generated.contains("// MARK: - Traversal Helpers"))
        XCTAssertTrue(generated.contains("""
private func parent1(_ component: NeedleFoundation.Scope) -> NeedleFoundation.Scope {
    return component.parent
}
"""))
        XCTAssertTrue(generated.contains("""
private func parent2(_ component: NeedleFoundation.Scope) -> NeedleFoundation.Scope {
    return component.parent.parent
}
"""))
        XCTAssertTrue(generated.contains("""
private func parent3(_ component: NeedleFoundation.Scope) -> NeedleFoundation.Scope {
    return component.parent.parent.parent
}
"""))
        XCTAssertFalse(generated.contains("private func parent4(_ component: NeedleFoundation.Scope) -> NeedleFoundation.Scope {"))
        XCTAssertTrue(generated.contains("// MARK: - Traversal Helpers"))
        XCTAssertTrue(generated.contains("// MARK: - Providers"))
        XCTAssertTrue(generated.contains("""
private class LoggedOutDependencyacada53ea78d270efa2fProvider: LoggedOutDependency {
    var mutablePlayersStream: MutablePlayersStream {
        return rootComponent.mutablePlayersStream
    }
    private let rootComponent: RootComponent
    init(rootComponent: RootComponent) {
        self.rootComponent = rootComponent
    }
}
/// ^->RootComponent->LoggedOutComponent
private func factory1434ff4463106e5c4f1bb3a8f24c1d289f2c0f2e(_ component: NeedleFoundation.Scope) -> AnyObject {
    return LoggedOutDependencyacada53ea78d270efa2fProvider(rootComponent: parent1(component) as! RootComponent)
}
"""))
        XCTAssertTrue(generated.contains("""
private class ScoreSheetDependencyea879b8e06763171478bProvider: ScoreSheetDependency {
    var scoreStream: ScoreStream {
        return (loggedInComponent.nonCoreComponent as! LoggedInNonCoreComponent).scoreStream
    }
    private let loggedInComponent: LoggedInComponent
    init(loggedInComponent: LoggedInComponent) {
        self.loggedInComponent = loggedInComponent
    }
}
/// ^->RootComponent->LoggedInComponent->GameComponent->GameNonCoreComponent->ScoreSheetComponent
private func factoryb11b7d1dec7e3c9b3dca49b41e44e0ed6a6f8eaf(_ component: NeedleFoundation.Scope) -> AnyObject {
    return ScoreSheetDependencyea879b8e06763171478bProvider(loggedInComponent: parent3(component) as! LoggedInComponent)
}
"""))
        XCTAssertTrue(generated.contains("""
private class ScoreSheetDependency6fb80fa6e1ee31d9ba11Provider: ScoreSheetDependency {
    var scoreStream: ScoreStream {
        return loggedInNonCoreComponent.scoreStream
    }
    private let loggedInNonCoreComponent: LoggedInNonCoreComponent
    init(loggedInNonCoreComponent: LoggedInNonCoreComponent) {
        self.loggedInNonCoreComponent = loggedInNonCoreComponent
    }
}
/// ^->RootComponent->LoggedInComponent->LoggedInNonCoreComponent->ScoreSheetComponent
private func factory3306c50e89e2421d0b0c65d055996113f3c13de1(_ component: NeedleFoundation.Scope) -> AnyObject {
    return ScoreSheetDependency6fb80fa6e1ee31d9ba11Provider(loggedInNonCoreComponent: parent1(component) as! LoggedInNonCoreComponent)
}
"""))
        XCTAssertTrue(generated.contains("""
private class GameDependency1ab5926a977f706d3195Provider: GameDependency {
    var mutableScoreStream: MutableScoreStream {
        return loggedInComponent.pluginExtension.mutableScoreStream
    }
    var playersStream: PlayersStream {
        return rootComponent.playersStream
    }
    private let loggedInComponent: LoggedInComponent
    private let rootComponent: RootComponent
    init(loggedInComponent: LoggedInComponent, rootComponent: RootComponent) {
        self.loggedInComponent = loggedInComponent
        self.rootComponent = rootComponent
    }
}
/// ^->RootComponent->LoggedInComponent->GameComponent
private func factorycf9c02c4def4e3d508816cd03d3cf415b70dfb0e(_ component: NeedleFoundation.Scope) -> AnyObject {
    return GameDependency1ab5926a977f706d3195Provider(loggedInComponent: parent1(component) as! LoggedInComponent, rootComponent: parent2(component) as! RootComponent)
}
"""))
        XCTAssertTrue(generated.contains("""
/// GameComponent plugin extension
private class GamePluginExtensionProvider: GamePluginExtension {
    var scoreSheetBuilder: ScoreSheetBuilder {
        return gameNonCoreComponent.scoreSheetBuilder
    }
    private unowned let gameNonCoreComponent: GameNonCoreComponent
    init(component: NeedleFoundation.Scope) {
        let gameComponent = component as! GameComponent
        gameNonCoreComponent = gameComponent.nonCoreComponent as! GameNonCoreComponent
    }
}
"""))
        XCTAssertTrue(generated.contains("""
/// LoggedInComponent plugin extension
private class LoggedInPluginExtensionProvider: LoggedInPluginExtension {
    var scoreSheetBuilder: ScoreSheetBuilder {
        return loggedInNonCoreComponent.scoreSheetBuilder
    }
    var mutableScoreStream: MutableScoreStream {
        return loggedInNonCoreComponent.mutableScoreStream
    }
    private unowned let loggedInNonCoreComponent: LoggedInNonCoreComponent
    init(component: NeedleFoundation.Scope) {
        let loggedInComponent = component as! LoggedInComponent
        loggedInNonCoreComponent = loggedInComponent.nonCoreComponent as! LoggedInNonCoreComponent
    }
}
"""))
    }

    @available(OSX 10.12, *)
    func test_export_verifyFileWriteBehavior() throws {
        let (components, pluginizedComponents, imports, needleHash, allDeps) = pluginizedSampleProjectParsed()
        let executor = MockSequenceExecutor()
        let exporter = PluginizedDependencyGraphExporter()
        let headerDocPath = fixturesURL.deletingLastPathComponent().appendingPathComponent("HeaderDoc.txt").path

        try exporter.export(components, pluginizedComponents, with: imports, to: outputURL.path, using: executor, withTimeout: 10, include: headerDocPath, needleVersionHash: needleHash)
        let generated = try String(contentsOf: outputURL)

        XCTAssertNotNil(generated, "Could not read the generated file")

        let expectedAttributes = try FileManager.default.attributesOfItem(atPath: outputURL.path)
        let expectedCreationDate = (expectedAttributes[.creationDate] as! Date).timeIntervalSinceReferenceDate
        let expectedModificationDate = (expectedAttributes[.modificationDate] as! Date).timeIntervalSinceReferenceDate

        try exporter.export(components, pluginizedComponents, with: imports, to: outputURL.path, using: executor, withTimeout: 10, include: headerDocPath, needleVersionHash: needleHash)

        let attributes = try FileManager.default.attributesOfItem(atPath: outputURL.path)
        let creationDate = (attributes[.creationDate] as! Date).timeIntervalSinceReferenceDate
        let modificationDate = (attributes[.modificationDate] as! Date).timeIntervalSinceReferenceDate

        XCTAssertEqual(creationDate, expectedCreationDate)
        XCTAssertEqual(modificationDate, expectedModificationDate)
    }
}
